﻿using System;
using System.ComponentModel.Design;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Runtime.InteropServices;
using EnvDTE;
using EnvDTE80;
using Microsoft.VisualStudio;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using WindowsPhoneToolbox.Util;
using AppRes = WindowsPhoneToolbox.Resources;
using Thread = System.Threading.Thread;

namespace WindowsPhoneToolbox
{
    /// <summary>
    /// This is the class that implements the package exposed by this assembly.
    ///
    /// The minimum requirement for a class to be considered a valid package for Visual Studio
    /// is to implement the IVsPackage interface and register itself with the shell.
    /// This package uses the helper classes defined inside the Managed Package Framework (MPF)
    /// to do it: it derives from the Package class that provides the implementation of the 
    /// IVsPackage interface and uses the registration attributes defined in the framework to 
    /// register itself and its components with the shell.
    /// </summary>
    // This attribute tells the PkgDef creation utility (CreatePkgDef.exe) that this class is
    // a package.
    [PackageRegistration(UseManagedResourcesOnly = false)]
    // This attribute is used to register the informations needed to show the this package
    // in the Help/About dialog of Visual Studio.
    [InstalledProductRegistration("#110", "#112", "1.0", IconResourceID = 400)]
    // This attribute is needed to let the shell know that this package exposes some menus.
    [ProvideMenuResource("Menus.ctmenu", 1)]
    // This attribute registers a tool window exposed by this package.
    [ProvideToolWindow(typeof(WindowPhoneToolWindow))]
    [Guid(GuidList.GuidWindowsPhoneToolboxPkgString)]
    public sealed class WindowsPhoneToolboxPackage : Package, IVsShellPropertyEvents, IVsSolutionEvents,
                                                     IVsUpdateSolutionEvents2, IVsDebuggerEvents
    {
        #region Fileds

        private double _currProject;
        private uint _debugEventsCookie;

        private IVsSolutionBuildManager2 _sbm;
        private uint _shellPropertyChangesCookie;
        private IVsSolution2 _solution;
        private uint _solutionEventsCookie;

        private WindowPhoneToolWindow _toolWindow;

        private int _totalProjects;
        private uint _updateSolutionEventsCookie;
        private int _visualEffectsAllowed;
        private IVsDebugger _vsDebugger;
        private IVsShell _vsShell;

        private string _projectNameStartup;
        private bool _tryConnect;

        public static DBGMODE DbgmodeNew = DBGMODE.DBGMODE_Design;

        #endregion

        #region Constructor

        /// <summary>
        /// Default constructor of the package.
        /// Inside this method you can place any initialization code that does not require 
        /// any Visual Studio service because at this point the package object is created but 
        /// not sited yet inside Visual Studio environment. The place to do all the other 
        /// initialization is the Initialize method.
        /// </summary>
        public WindowsPhoneToolboxPackage()
        {
            Trace.WriteLine(String.Format(CultureInfo.CurrentCulture, "Entering constructor for: {0}", ToString()));
        }

        #endregion

        #region IVsShellPropertyEvents Members

        int IVsShellPropertyEvents.OnShellPropertyChange(int propid, object var)
        {
            // If the propid matches the value we're interested in, query the new value
            if (propid == (int) __VSSPROPID4.VSSPROPID_VisualEffectsAllowed)
            {
                _visualEffectsAllowed = (int) var;
                _toolWindow.EffectsEnabled = _visualEffectsAllowed != 0;
            }

            return VSConstants.S_OK;
        }

        #endregion

        #region IVsSolutionEvents Members

        int IVsSolutionEvents.OnAfterCloseSolution(object pUnkReserved)
        {
            // Reset progress bar after closing solution
            _totalProjects = 0;

            if (_toolWindow != null)
            {
                _toolWindow.BarText = "";
                _toolWindow.Progress = 0;
                _toolWindow.ShowBuilding(false);
                if (!_toolWindow.HasSolution())
                {
                    _toolWindow.ShowPhoneConnect(true);
                }
            }

            return VSConstants.S_OK;
        }

        int IVsSolutionEvents.OnAfterLoadProject(IVsHierarchy pStubHierarchy, IVsHierarchy pRealHierarchy)
        {
            return VSConstants.S_OK;
        }

        int IVsSolutionEvents.OnAfterOpenProject(IVsHierarchy pHierarchy, int fAdded)
        {
            // Track the number of open projects
            _totalProjects++;
            return VSConstants.S_OK;
        }

        int IVsSolutionEvents.OnAfterOpenSolution(object pUnkReserved, int fNewSolution)
        {
            return VSConstants.S_OK;
        }

        int IVsSolutionEvents.OnBeforeCloseProject(IVsHierarchy pHierarchy, int fRemoved)
        {
            // Track the number of open projects
            _totalProjects--;
            return VSConstants.S_OK;
        }

        int IVsSolutionEvents.OnBeforeCloseSolution(object pUnkReserved)
        {
            return VSConstants.S_OK;
        }

        int IVsSolutionEvents.OnBeforeUnloadProject(IVsHierarchy pRealHierarchy, IVsHierarchy pStubHierarchy)
        {
            return VSConstants.S_OK;
        }

        int IVsSolutionEvents.OnQueryCloseProject(IVsHierarchy pHierarchy, int fRemoving, ref int pfCancel)
        {
            return VSConstants.S_OK;
        }

        int IVsSolutionEvents.OnQueryCloseSolution(object pUnkReserved, ref int pfCancel)
        {
            return VSConstants.S_OK;
        }

        int IVsSolutionEvents.OnQueryUnloadProject(IVsHierarchy pRealHierarchy, ref int pfCancel)
        {
            return VSConstants.S_OK;
        }

        #endregion

        #region IVsUpdateSolutionEvents2 Members

        int IVsUpdateSolutionEvents2.OnActiveProjectCfgChange(IVsHierarchy pIVsHierarchy)
        {
            return VSConstants.S_OK;
        }

        int IVsUpdateSolutionEvents2.UpdateProjectCfg_Begin(IVsHierarchy pHierProj, IVsCfg pCfgProj, IVsCfg pCfgSln,
                                                            uint dwAction, ref int pfCancel)
        {
            // This method is called when a specific project begins building.  Based on the total number of open projects, we can estimate
            // how far along in the build we are.
            _currProject++;

            if (_toolWindow != null)
            {
                // Update progress bar text
                object o;
                pHierProj.GetProperty((uint) VSConstants.VSITEMID.Root, (int) __VSHPROPID.VSHPROPID_Name, out o);
                var name = o as string;
                _toolWindow.BarText = "Building " + name + "...";

                // Update bar value; estimate percentage completion
                if (_totalProjects != 0)
                {
                    _toolWindow.Progress = _currProject / (_totalProjects * 2);
                }
            }

            return VSConstants.S_OK;
        }

        int IVsUpdateSolutionEvents2.UpdateProjectCfg_Done(IVsHierarchy pHierProj, IVsCfg pCfgProj, IVsCfg pCfgSln,
                                                           uint dwAction, int fSuccess, int fCancel)
        {
            // This method is called when a specific project finishes building.  Move the progress bar value accordginly.
            if (_toolWindow != null)
            {
                _toolWindow.BarText = "";

                if (_totalProjects != 0)
                {
                    _toolWindow.Progress = (++_currProject)/(_totalProjects*2);
                }
            }

            return VSConstants.S_OK;
        }

        int IVsUpdateSolutionEvents2.UpdateSolution_Begin(ref int pfCancelUpdate)
        {
            return VSConstants.S_OK;
        }

        int IVsUpdateSolutionEvents2.UpdateSolution_Cancel()
        {
            return VSConstants.S_OK;
        }

        int IVsUpdateSolutionEvents2.UpdateSolution_Done(int fSucceeded, int fModified, int fCancelCommand)
        {
            return VSConstants.S_OK;
        }

        int IVsUpdateSolutionEvents2.UpdateSolution_StartUpdate(ref int pfCancelUpdate)
        {
            return VSConstants.S_OK;
        }

        int IVsUpdateSolutionEvents.OnActiveProjectCfgChange(IVsHierarchy pIVsHierarchy)
        {
            return VSConstants.S_OK;
        }

        int IVsUpdateSolutionEvents.UpdateSolution_Begin(ref int pfCancelUpdate)
        {
            //find startup project when F5
            _projectNameStartup = String.Empty;
            IVsHierarchy ppHierarchy;
            _sbm.get_StartupProject(out ppHierarchy);
            var aggregatableProject = (IVsAggregatableProject)ppHierarchy;
            string projectTypeGuids;
            aggregatableProject.GetAggregateProjectTypeGuids(out projectTypeGuids);
            //windows phone C#/VB Template Project
            if (projectTypeGuids.Contains("{C089C8C0-30E0-4E22-80C0-CE093F111A43}") || projectTypeGuids.Contains("{DB03555F-0C8B-43BE-9FF9-57896B3C5E56}"))
            {
                object o;
                ppHierarchy.GetProperty((uint)VSConstants.VSITEMID.Root, (int)__VSHPROPID.VSHPROPID_Name, out o);
                _projectNameStartup = (string)o;
            }

            _tryConnect = false;

            // This method is called when the entire solution starts to build.
            _currProject = 0;
            _toolWindow.Progress = 0;
            if (_toolWindow != null && !String.IsNullOrEmpty(_projectNameStartup))
            {
                _tryConnect = true;
                _toolWindow.ShowBuilding(true);
                _toolWindow.BarText = "Starting build.";
            }
            return VSConstants.S_OK;
        }

        int IVsUpdateSolutionEvents.UpdateSolution_Cancel()
        {
            return VSConstants.S_OK;
        }

        int IVsUpdateSolutionEvents.UpdateSolution_Done(int fSucceeded, int fModified, int fCancelCommand)
        {
            // This method is called when the entire solution is done building.
            if (_toolWindow != null)
            {
                if (fSucceeded != 0)
                {
                    _toolWindow.BarText = "Build completed.";
                    _toolWindow.Progress = 1;
                }
                else if (fCancelCommand != 0)
                {
                    _toolWindow.BarText = "Build canceled.";
                    _tryConnect = false;
                }
                Thread.Sleep(500);
                _toolWindow.ShowBuilding(false);
                
            }
            return VSConstants.S_OK;
        }

        int IVsUpdateSolutionEvents.UpdateSolution_StartUpdate(ref int pfCancelUpdate)
        {
            return VSConstants.S_OK;
        }

        #endregion

        #region Implementation of IVsDebuggerEvents

        public int OnModeChange(DBGMODE dbgmodeNew)
        {
            if (_tryConnect)
            {
                if (_toolWindow != null)
                {
                    if (!String.IsNullOrEmpty(_projectNameStartup))
                    {
                        var pathmanifest = String.Empty;
                        var dte  = GetGlobalService(typeof(SDTE)) as DTE2;
                        // ReSharper disable PossibleNullReferenceException
                        var prjs = dte.Solution.Projects;
                        // ReSharper restore PossibleNullReferenceException
                        foreach (var items in from Project proj in prjs where proj.Name == _projectNameStartup select proj.ProjectItems.GetEnumerator())
                        {
                            while (items.MoveNext())
                            {
                                var result = Common.GetPathFileManifest((ProjectItem)items.Current);
                                if (!String.IsNullOrEmpty(result))
                                {
                                    pathmanifest = result;
                                    break;
                                }
                            }
                        }
                        _toolWindow.ShowPhoneStatus(true);
                        _toolWindow.ShowPhoneConnect(true);
                        if (!string.IsNullOrEmpty(pathmanifest))
                        {
                            var targetDevice = Common.GetTargetDevice(dte);
                            if (!string.IsNullOrEmpty(targetDevice))
                            {
                                _toolWindow.Connect(pathmanifest, targetDevice);
                            }
                        }
                    }
                }
                _tryConnect = false;
            }

            DbgmodeNew = dbgmodeNew;

            if (_toolWindow != null) _toolWindow.StatusText = Common.GetStatus();

            return VSConstants.S_OK;
        }

        #endregion

        #region Dispose / Initialize

        protected override void Dispose(bool disposing)
        {
            base.Dispose(disposing);

            // Unadvise all events
            if (_vsShell != null && _shellPropertyChangesCookie != 0)
                _vsShell.UnadviseShellPropertyChanges(_shellPropertyChangesCookie);

            if (_sbm != null && _updateSolutionEventsCookie != 0)
                _sbm.UnadviseUpdateSolutionEvents(_updateSolutionEventsCookie);

            if (_solution != null && _solutionEventsCookie != 0)
                _solution.UnadviseSolutionEvents(_solutionEventsCookie);

            if (_vsDebugger != null && _debugEventsCookie != 0)
                _vsDebugger.UnadviseDebuggerEvents(_debugEventsCookie);
        }

        /// <summary>
        /// This function is called when the user clicks the menu item that shows the 
        /// tool window. See the Initialize method to see how the menu item is associated to 
        /// this function using the OleMenuCommandService service and the MenuCommand class.
        /// </summary>
        private void ShowToolWindow(object sender, EventArgs e)
        {
            // Get the instance number 0 of this tool window. This window is single instance so this instance
            // is actually the only one.
            // The last flag is set to true so that if the tool window does not exists it will be created.
            _toolWindow = FindToolWindow(typeof (WindowPhoneToolWindow), 0, true) as WindowPhoneToolWindow;
            if ((null == _toolWindow) || (null == _toolWindow.Frame))
            {
                throw new NotSupportedException(Resources.CanNotCreateWindow);
            }
            var windowFrame = (IVsWindowFrame) _toolWindow.Frame;
            ErrorHandler.ThrowOnFailure(windowFrame.Show());
            _toolWindow.ShowWelComeTimer(2);
        }

        /////////////////////////////////////////////////////////////////////////////
        // Overriden Package Implementation

        /// <summary>
        /// Initialization of the package; this method is called right after the package is sited, so this is the place
        /// where you can put all the initilaization code that rely on services provided by VisualStudio.
        /// </summary>
        /// 
        protected override void Initialize()
        {
            Trace.WriteLine(String.Format(CultureInfo.CurrentCulture, "Entering Initialize() of: {0}", ToString()));
            base.Initialize();

            // Add our command handlers for menu (commands must exist in the .vsct file)
            var mcs = GetService(typeof (IMenuCommandService)) as OleMenuCommandService;
            if (null != mcs)
            {
                // Create the command for the tool window
                var toolwndCommandId = new CommandID(GuidList.GuidWindowsPhoneToolboxCmdSet,
                                                     (int) PkgCmdIdList.CmdidWindowPhoneToolbox);
                var menuToolWin = new MenuCommand(ShowToolWindow, toolwndCommandId);
                mcs.AddCommand(menuToolWin);
            }

            // Get shell object
            _vsShell = ServiceProvider.GlobalProvider.GetService(typeof (SVsShell)) as IVsShell;
            if (_vsShell != null)
            {
                // Initialize VisualEffects values, so themes can determine if various effects are supported by the environment
                object effectsAllowed;
                if (
                    ErrorHandler.Succeeded(_vsShell.GetProperty((int) __VSSPROPID4.VSSPROPID_VisualEffectsAllowed,
                                                               out effectsAllowed)))
                {
                    // VSSPROPID_VisualEffectsAllowed is a VT_I4 property, so casting to int should be safe
                    Debug.Assert(effectsAllowed is int, "VSSPROPID_VisualEffectsAllowed should be of type int");
                    _visualEffectsAllowed = (int) effectsAllowed;
                }
                else
                {
                    Debug.Fail("Failed to get the VSSPROPID_VisualEffectsAllowed property value.");
                }

                // Subscribe to shell property changes to update VisualEffects values if the user modifies the settings
                _vsShell.AdviseShellPropertyChanges(this, out _shellPropertyChangesCookie);
            }

            // Get solution
            _solution = ServiceProvider.GlobalProvider.GetService(typeof (SVsSolution)) as IVsSolution2;
            if (_solution != null)
            {
                // Get count of any currently loaded projects
                object count;
                _solution.GetProperty((int) __VSPROPID.VSPROPID_ProjectCount, out count);
                _totalProjects = (int) count;

                // Register for solution events
                _solution.AdviseSolutionEvents(this, out _solutionEventsCookie);
            }

            // Get solution build manager
            _sbm =
                ServiceProvider.GlobalProvider.GetService(typeof (SVsSolutionBuildManager)) as IVsSolutionBuildManager2;
            if (_sbm != null)
            {
                _sbm.AdviseUpdateSolutionEvents(this, out _updateSolutionEventsCookie);
            }

            // Get tool window
            if (_toolWindow == null)
            {
                _toolWindow = FindToolWindow(typeof (WindowPhoneToolWindow), 0, true) as WindowPhoneToolWindow;
            }

            // Set initial value of EffectsEnabled in tool window
            if (_toolWindow != null) _toolWindow.EffectsEnabled = _visualEffectsAllowed != 0;

            // get Debuger Manager
            _vsDebugger = ServiceProvider.GlobalProvider.GetService(typeof (IVsDebugger)) as IVsDebugger;
            if (_vsDebugger != null)
            {
                _vsDebugger.AdviseDebuggerEvents(this, out _debugEventsCookie);
            }
        }

        #endregion

    }
}